package org.nutz.dao.impl.sql;

import org.nutz.dao.DaoException;
import org.nutz.dao.FieldMatcher;
import org.nutz.dao.entity.Entity;
import org.nutz.dao.entity.LinkField;
import org.nutz.dao.entity.LinkVisitor;
import org.nutz.dao.entity.MappingField;
import org.nutz.dao.impl.sql.pojo.NoParamsPItem;
import org.nutz.dao.jdbc.JdbcExpert;
import org.nutz.dao.sql.Pojo;
import org.nutz.dao.sql.PojoCallback;
import org.nutz.dao.sql.PojoMaker;
import org.nutz.dao.sql.SqlType;
import org.nutz.dao.util.Pojos;
import org.nutz.lang.*;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class NutPojoMaker implements PojoMaker {

    private JdbcExpert expert;

    public NutPojoMaker(JdbcExpert expert) {
        this.expert = expert;
    }

    public Pojo makePojo(SqlType type) {
        return expert.createPojo(type);
    }

    public Pojo makeInsert(final Entity<?> en) {
        Pojo pojo = Pojos.pojo(expert, en, SqlType.INSERT);
        pojo.setEntity(en);
        pojo.append(Pojos.Items.entityTableName());
        pojo.append(Pojos.Items.insertFields());
        pojo.append(Pojos.Items.insertValues());
        if (expert.isSupportAutoIncrement()) {
        	MappingField mf = en.getIdField();
        	if (mf != null && mf.isAutoIncreasement()) {
        	    if (expert.isSupportGeneratedKeys()) {
                    pojo.setAfter(new GeneratedKeys());
                    pojo.getContext().attr("RETURN_GENERATED_KEYS", true);
        	    }
        	}
        }
        return pojo;
    }

    public Pojo makeUpdate(Entity<?> en, Object refer) {
        Pojo pojo = Pojos.pojo(expert, en, SqlType.UPDATE);
        pojo.setEntity(en);
        pojo.append(Pojos.Items.entityTableName());
        pojo.append(Pojos.Items.updateFields(refer));
        return pojo;
    }

    public Pojo makeQuery(Entity<?> en) {
        Pojo pojo = Pojos.pojo(expert, en, SqlType.SELECT);
        pojo.setEntity(en);
        pojo.append(Pojos.Items.queryEntityFields());
        pojo.append(Pojos.Items.wrap("FROM"));
        pojo.append(Pojos.Items.entityViewName());
        return pojo;
    }
    
    public Pojo makeQuery(String tableName) {
        return makeQuery(tableName, "*");
    }

    public Pojo makeQuery(String tableName, String fields) {
        String[] ss = tableName.split(":");
        // String idFieldName = ss.length > 1 ? ss[1] : "*";//按id字段来统计,比较快
        Pojo pojo = makePojo(SqlType.SELECT);
        // pojo.append(Pojos.Items.wrap(idFieldName));//与org.nutz.dao.test.normal.QueryTest.query_records_pager()冲突
        pojo.append(Pojos.Items.wrap(fields));
        pojo.append(Pojos.Items.wrap("FROM"));
        pojo.append(Pojos.Items.wrap(ss[0]));
        return pojo;
    }

    public Pojo makeDelete(Entity<?> en) {
        Pojo pojo = Pojos.pojo(expert, en, SqlType.DELETE);
        pojo.setEntity(en);
        pojo.append(Pojos.Items.wrap("FROM"));
        pojo.append(Pojos.Items.entityTableName());
        return pojo;
    }

    public Pojo makeDelete(String tableName) {
        Pojo pojo = makePojo(SqlType.DELETE);
        pojo.append(Pojos.Items.wrap("FROM"));
        pojo.append(Pojos.Items.wrap(tableName));
        return pojo;
    }

    public Pojo makeFunc(String tableName, String funcName, String colName) {
        Pojo pojo = makePojo(SqlType.SELECT);
        pojo.append(Pojos.Items.wrapf("%s(%s) FROM %s", funcName, colName, tableName));
        return pojo;
    }

    static class GeneratedKeys implements PojoCallback {
    	
    	public Object invoke(Connection conn, ResultSet rs, final Pojo pojo, Statement stmt) throws SQLException {
			final ResultSet _rs = stmt.getGeneratedKeys();
			Object obj = pojo.getOperatingObject();
			if (obj instanceof Map) {
			    obj = Arrays.asList(obj);
			}
			Lang.each(obj, new Each<Object>() {
				public void invoke(int index, Object ele, int length) throws ExitLoop, ContinueLoop, LoopException {
					try {
						if (!_rs.next())
							throw new ExitLoop();
						Object key = _rs.getObject(1);
						pojo.getEntity().getIdField().setValue(ele, key);
					} catch (SQLException e) {
						throw new DaoException(e);
					}
				}
			});
			return pojo.getOperatingObject();
		}
    }
    
    @Override
    public Pojo makeQueryByJoin(final Entity<?> en, String regex) {
        final Pojo pojo = Pojos.pojo(expert, en, SqlType.SELECT);
        pojo.setEntity(en);
        pojo.append(new QueryJoinFeilds(en, true));
        final int[] index = new int[1];
        en.visitOne(null, regex, new LinkVisitor() {
            public void visit(Object obj, LinkField lnk) {
                pojo.append(Pojos.Items.wrap(","));
                pojo.append(new QueryJoinFeilds(lnk.getLinkedEntity(), false));
                index[0]++;
            }
        });
        pojo.append(Pojos.Items.wrap("FROM"));
        pojo.append(Pojos.Items.entityViewName());
        index[0] = 0;
        en.visitOne(null, regex, new LinkVisitor() {
            public void visit(Object obj, LinkField lnk) {
                Entity<?> lnkEntity = lnk.getLinkedEntity();
                String LJ = String.format("LEFT JOIN %s ON %s.%s = %s.%s", 
                                          lnkEntity.getTableName(), 
                                          en.getTableName(), lnk.getHostField().getColumnNameInSql(),
                                          lnkEntity.getTableName(), lnk.getLinkedField().getColumnNameInSql());
                pojo.append(Pojos.Items.wrap(LJ));
                index[0]++;
            }
        });
        return pojo;
    }
    
    @SuppressWarnings("serial")
    protected static class QueryJoinFeilds extends NoParamsPItem {
        
        protected Entity<?> en;
        protected boolean main;
        
        public QueryJoinFeilds(Entity<?> en, boolean main) {
            this.en = en;
            this.main = main;
        }

        public void joinSql(Entity<?> en, StringBuilder sb) {
            en = this.en;
            FieldMatcher fm = getFieldMatcher();
            List<MappingField> efs = _en(en).getMappingFields();

            int old = sb.length();

            for (MappingField ef : efs) {
                if (fm == null || fm.match(ef.getName())) {
                    sb.append(en.getTableName()).append(".").append(ef.getColumnNameInSql()).append(" as ");
                    if (!main)
                        sb.append(en.getTableName()).append("_z_");
                    sb.append(ef.getColumnNameInSql()).append(',');
                }
            }

            if (sb.length() == old)
                throw Lang.makeThrow("No columns be queryed: '%s'", _en(en));

            sb.setCharAt(sb.length() - 1, ' ');
        }

    }
}
